home *** CD-ROM | disk | FTP | other *** search
- /*
- FezEdit: Frame-Evading Zoomrects Editor
-
- by Douglas McKenna
- (c) Copyright 1994 All rights reserved.
-
- Mathemaesthetics, Inc.
- PO Box 298 • Boulder • CO • 80306-0298 • USA
-
- Submitted as a hack and a half for MacHack, June 24, 1994
- Winner (by popular acclaim) of Best Hack of the Contest
-
- Permission is granted to use this code in any non-commercial product.
- Please contact the author for prior written approval for use in any
- commercial or shareware product.
-
- This file contains the FEZ routines for implementing various forms
- of Zooming Rects.
- */
-
-
-
- #include "Fez.h"
- #include "Utilities.h"
-
- // Local globals
-
- static GrafPort deskPort; // Temporary port to do all desktop drawing in
- static RgnHandle deskClip; // Its current clipping region
-
- static RgnHandle qClip[MAXQUEUE];
- static Rect qRect[MAXQUEUE];
-
- // These macros perform the conversions on the frame within the
- // window of a ZoomFrame struct using the utility routines
-
- #define GlobalToLocalZF(zf) GlobalToLocalRect((zf)->win, &(zf)->frame)
- #define LocalToGlobalZF(zf) LocalToGlobalRect((zf)->win, &(zf)->frame)
-
- #define CONTROLINSET 16
-
- // Prototypes for local FEZ utility routines
-
- int InstallDesktop(void);
- void ResetDesktop(void);
-
- ///////////////////////////////////////////////////////////////////////////////
- /*
- * Create a new port the size of the current desktop, and initialize
- * its drawing environment for drawing and erasing gray zoomrects.
- * Deliver TRUE if all goes well, FALSE if memory or other problem.
- * You must match a successful call to InstallDeskTop with a call
- * to ResetDesktop.
- */
-
- static int InstallDesktop()
- {
- RgnHandle deskGray = LMGetGrayRgn(); // New low-mem accessor function/macro
-
- PushPort(NIL); // Save whatever the current port is
- OpenPort(&deskPort); // Sets the current port to deskPort
- if (MemError()) {
- PopPort();
- return(FALSE);
- }
-
- CopyRgn(deskGray,deskPort.visRgn); // Make it the same as GrayRgn
- if (MemError()) {
- ClosePort(&deskPort); // Back out, no memory
- PopPort();
- return(FALSE);
- }
- deskPort.portRect = (*deskGray)->rgnBBox;
-
- // Get a copy of desktop clipping region
-
- deskClip = NewRgn();
- if (deskClip) {
- GetClip(deskClip);
- PenPat(&qd.gray); // Draw in gray pattern
- PenMode(notPatXor); // Using xor mode
- }
- else {
- ClosePort(&deskPort); // Back out, no memory
- PopPort();
- }
-
- return(deskClip != NIL);
- }
-
- /*
- * Restore the drawing environment in effect when InstallDesktop() was called
- */
-
- void ResetDesktop()
- {
- DisposeRgn(deskClip);
- ClosePort(&deskPort);
- PopPort();
- }
-
- /*
- * Instead of interpolating between the four corners of the two limiting
- * rects, we interpolate along a line between the two limiting rects’ centers,
- * and between their two sizes. This code doesn't care about the
- * direction of the zoom, or whether either of the Rect's is empty.
- */
-
- void ZoomCenterRect(Rect *startRect, Rect *endRect, short thick, short speed, short qSize)
- {
- Point startCenter,endCenter,startSize,endSize;
- short zoomSteps,x,y,w,h,q; long i;
-
- InstallDesktop(); // Draw outside of all windows
-
- // Get centers of the two limiting rectangles
-
- startCenter.h = (startRect->left + startRect->right) / 2;
- startCenter.v = (startRect->top + startRect->bottom) / 2;
- endCenter.h = (endRect->left + endRect->right) / 2;
- endCenter.v = (endRect->top + endRect->bottom) / 2;
-
- // And the starting and ending half sizes
-
- startSize.h = (startRect->right - startRect->left) / 2;
- startSize.v = (startRect->bottom - startRect->top) / 2;
- endSize.h = (endRect->right - endRect->left) / 2;
- endSize.v = (endRect->bottom - endRect->top) / 2;
-
- // Fill the rectangle queue with empty rectangles so nothing gets drawn
- // initially as the queue fills up. The queue makes for a better zoom
- // effect, since there will be more graphic weight to the zooming, and
- // the amount of time any particular rectangle is displayed is much longer.
-
- if (qSize < 1) qSize = 1;
- SetRect(&qRect[0],0,0,0,0);
- for (q=1; q<qSize; q++)
- qRect[q] = qRect[0];
-
- PenSize(thick,thick); // Normal value should be (1,1)
-
- zoomSteps = 16;
- for (i=0; i<=zoomSteps; i++) { // loops zoomSteps+1 times
-
- // The following arithmetic is susceptable to overflow for short
- // coordinates with large magnitudes (e.g. greater than (32K/zoomSteps),
- // so we do our intermediate calculations in longs by making i a long.
- // If zoomSteps is a power of 2, it is also a lot faster to replace
- // the divide-by-zoomSteps with a right-shift by that power of 2,
- // although it doesn't really matter since the loop has a 1-tick governor.
-
- // Find the i'th intermediate position along line between centers
- x = ((zoomSteps-i)*startCenter.h + i*endCenter.h) / zoomSteps;
- y = ((zoomSteps-i)*startCenter.v + i*endCenter.v) / zoomSteps;
- // And i'th intermediate sizes (interpolated linearly)
- w = ((zoomSteps-i)*startSize.h + i*endSize.h) / zoomSteps;
- h = ((zoomSteps-i)*startSize.v + i*endSize.v) / zoomSteps;
-
- // Build the next interpolated rectangle
- SetRect(&qRect[qSize-1],x-w,y-h,x+w,y+h);
- // Draw the newest zooming rectangle in the queue
- FrameRect(&qRect[qSize-1]);
- // Erase (assuming xor mode) the i-3'rd previously drawn rectangle
- FrameRect(&qRect[0]);
- // Shift rectangle queue up by 1, leaving r4 ready to be redefined
- for (q=1; q<qSize; q++)
- qRect[q-1] = qRect[q];
-
- // Use governor so processor speed doesn't affect zoom
- Wait(speed);
- if (speed > 5)
- {
- EventRecord event;
- while (!GetNextEvent(keyDownMask,&event))
- SystemTask();
- // Allow system to handle a screen dump FKEY
- }
- }
-
- // Erase last three zoomrects to empty the queue of drawn zoomrects
-
- for (q=1; q<qSize; q++) {
- FrameRect(&qRect[q-1]);
- Wait(speed);
- }
-
- ResetDesktop(); // Restore normal drawing environment
- }
-
- /*
- * This function precomputes an array of ZoomFrames for doing various
- * types of zooms on the desktop between theWidget and theWindow frames.
- * The delivered array will have at least 2 entries, with the first and
- * last entries the same as the arguments. When opening is non-zero (TRUE),
- * then the zoom travels from theWidget to theWindow; otherwise, the zoom
- * travels from theWindow to theWidget. The delivered array must be
- * passed to FrameEvadingZoom() to perform the actual zoom drawing later,
- * using the zoom style provided here.
- *
- * The caller must fill in the initial .win, .frame, and .thickness fields
- * of each ZoomFrame argument before passing them into this routine.
- * The frame rectangle for theWidget should be in LOCAL coordinates with
- * respect to theWidget->win. The frame rectangle for theWindow should
- * be in GLOBAL screen coordinates. In either case, theWindow->win
- * should be in its final or current position in the Window List.
- * If opening, you will want to show the window after doing the zoom;
- * if closing, you will want to hide the window before doing the zoom.
- *
- * By calling this first, before drawing the zoom, we avoid having
- * to worry about changes to the window order messing us up when the
- * caller eventually calls HideWindow (which changes the window order)
- * prior to doing the actual zoom using the array.
- *
- * The ZoomArray is allocated as a relocatable block on the heap and
- * the caller must dispose of it with DisposeZoom.
- *
- * zoomStyle is 0 for no zoom
- * 1 for standard linear zoom
- * 2 for one-window dive
- * 3 for full frame-evading
- *
- * Delivers NIL if not enough memory or other problem.
- */
-
- ZoomFrameHandle NewZoom(ZoomFrame *theWidget, ZoomFrame *theWindow, int opening, int zoomStyle)
- {
- RgnHandle clipRgn,tmpRgn; ZoomFrame *zf;
- ZoomFrameHandle zoomArray = NIL;
- short numFrames,w,h,width,height,x,y,k,i;
- WindowPeek wp; Rect ans,*bounds,*bbox;
- int okay = FALSE;
-
- // Reality checks
- if (theWidget==NIL || theWindow==NIL ||
- theWidget->win==NIL || theWindow->win==NIL ||
- theWidget->win==theWindow->win)
- return(NIL);
-
- // Install parameters in theWindow's private fields to pass to FEZ
- theWindow->opening = opening;
- theWindow->zoomStyle = zoomStyle;
-
- // Start with entire visible desktop as clipping region for theWindow
- clipRgn = NewRgn();
- if (clipRgn == NIL) goto cleanup;
- CopyRgn(LMGetGrayRgn(),clipRgn);
- if (MemError()) goto cleanup;
-
- // Subtract out all visible windows that are in front of theWindow.
- // These are typically floating palettes (or text input windows or balloons).
-
- wp = (WindowPeek)LMGetWindowList(); // For each window from front,
- while (wp!=(WindowPeek)theWindow->win && wp!=NIL) { // up to theWindow's,
- if (wp->visible) { // if it's visible,
- DiffRgn(clipRgn,wp->strucRgn,clipRgn); // cut out its structure region
- if (MemError()) goto cleanup;
- }
- wp = wp->nextWindow; // On to next window in list
- }
- if (wp == NIL) goto cleanup; // Reality check: should never happen
-
- // clipRgn now contains all pixels on desktop except those that belong
- // to windows in front of theWindow's.
-
- // Get maximum number of windows for which we might have to create array entries
-
- if (zoomStyle == 3) {
- numFrames = opening ? 2 : 1; // theWindow is invisible when opening
- wp = (WindowPeek)theWindow->win;
- while (wp!=(WindowPeek)theWidget->win && wp!=NIL) {
- if (wp->visible) numFrames++;
- wp = wp->nextWindow;
- }
- if (wp == NIL) {
- // Uh, ohh...theWidget->win is in front of theWindow->win or
- // not in the window list.
- // Not sure what the right thing to do is in this very unlikely
- // case, so we deliver NIL to do nothing, or do a standard zoom.
- goto cleanup;
- }
- if (numFrames < 2) {
- // Uh, ohh... theWidget->win was found, but was invisible
- // Skip town again
- goto cleanup;
- }
- }
- else
- numFrames = 2;
-
- // Determine whether the widget is wholly visible in its window or not.
- // theWidget's frame is expected to already be in local coordinates.
-
- theWidget->isHidden = TRUE;
- if (SectRect(&theWidget->win->portRect,&theWidget->frame,&ans))
- if (EqualRect(&ans,&theWidget->frame))
- theWidget->isHidden = FALSE;
-
- // Create maximum array storage for entries, initialized to 0.
- zoomArray = (ZoomFrameHandle)NewHandleClear(numFrames * sizeof(ZoomFrame));
- if (zoomArray == NIL) goto cleanup;
-
- // Place theWindow at start of array, regardless of zoom direction
- zf = *zoomArray;
- *zf = *theWindow;
- zf->clip = clipRgn; clipRgn = NIL; // Pass off clipRgn to first entry
-
- // Traverse down the window list until we hit the widget's window
- numFrames = 1;
- wp = (WindowPeek)theWindow->win;
- if (wp) wp = wp->nextWindow; // Skip first, we just did it above
- while (wp != (WindowPeek)theWidget->win) {
- if (wp->visible && zoomStyle==3) {
-
- // This is a good place to do any checks to cull windows that
- // pose no threat to the zoom path. This is a pretty hard
- // problem, but at the very least, we can ignore windows
- // that do not cover the widget's window's content region,
- // to which we will eventually be clipping.
- bounds = &(*wp->strucRgn)->rgnBBox;
- bbox = &(*((WindowPeek)theWidget->win)->contRgn)->rgnBBox;
- if (SectRect(bbox,bounds,&ans)) {
-
- // Intersection: need another knot in spline and intermediate ZF
- // Initialize the next intermediate ZoomRect's clipping region
- // as whatever we've got before, minus its window's structure.
-
- tmpRgn = NewRgn(); // Use tmp before dereference
- (*zoomArray)[numFrames].clip = tmpRgn;
- if (tmpRgn) {
- DiffRgn((*zoomArray)[numFrames-1].clip,wp->strucRgn,tmpRgn);
- if (MemError()) goto cleanup;
- }
- else
- goto cleanup;
-
- // Tell it which window it is evading, and keep same line width
-
- zf = (*zoomArray) + numFrames;
- zf->win = (WindowPtr)wp;
- zf->thickness = theWindow->thickness;
-
- // We will add information to the array after it has been
- // created, since in theory you may need to do some kind of
- // global search on the entire set of frames to find the best
- // spline path. In the meantime, we record the window's
- // structure region's bounds to be used below in the next stage.
-
- zf->frame = (*wp->strucRgn)->rgnBBox; // Global coordinates
-
- numFrames++;
- }
- }
- wp = wp->nextWindow;
- }
-
- // Set the last zoom to the ending ZoomFrame for widget, and cut the array down
- // to its final size, since we allocated a maximum number of entries above
- // but may have culled some windows above.
-
- (*zoomArray)[numFrames] = *theWidget;
- tmpRgn = NewRgn(); // Use temporary so we don't have to
- (*zoomArray)[numFrames].clip = tmpRgn; // lock zoomArray down during NewRgn.
-
- if (tmpRgn)
- CopyRgn((*zoomArray)[numFrames-1].clip,tmpRgn);
- else
- goto cleanup;
-
- // Cut the thing down to size
- numFrames++;
- SetHandleSize((Handle)zoomArray,numFrames*sizeof(ZoomFrame));
- HLock((Handle)zoomArray);
- // Convert theWidget's frame to global coords like all the other entries will be
- zf = (*zoomArray) + numFrames - 1;
- LocalToGlobalZF(zf);
-
- // Got our zoom array, now all we have left to do is compute the
- // frame positions next to the windows, and the knots and Bezier
- // control points within them. In order to evade each window frame,
- // the zoom has to find its way around the window, either above or
- // below or to the right or to the left (or we could use octants
- // for complete generality).
-
- zf = *zoomArray;
- for (i=0; i<numFrames; i++,zf++) { // For each ZoomFrame...
-
- // For all the internal frames, change the frame position.
- // The end frames are already in their final positions.
- if (i>0 && i<(numFrames-1)) {
-
- // Get width and height of window structure
- width = w = (zf->frame.right - zf->frame.left);
- height = h = (zf->frame.bottom - zf->frame.top);
-
- // For maximum amusement, we set the midway zooms to be
- // on various sides of the window frames, half size.
- // Basically, rotate our zoom around each successive window
- // This maximizes slinkyness and is very silly, but good for
- // demo purposes. This is the spot in the routine where it
- // would be appropriate to analyze the window pattern as a
- // whole to try to optimize a simple path that evades the
- // group as a whole to get to the destination frame. For
- // a small number of windows, you could even to a complete
- // backtrack search to find the shortest path, but I'll
- // leave that for another day.
-
- // Turn w and h into an offset to one side of window
-
- k = i & 3; // Cycle every four frames
- switch(k) {
- case 0: w = 0; h = -(h+16); break; // 16 pixels above window
- case 1: w = (w+16); h = 0; break; // 16 pixels to right of window
- case 2: w = 0; h = (h+16); break; // 16 pixels below window
- case 3: w = -(w+16); h = 0; break; // 16 pixels to left of window
- }
-
- OffsetRect(&zf->frame,w,h); // Move it 16 pixels outside window
- // Make the thing smaller than entire window bounds
- InsetRect(&zf->frame,width/4,height/4);
- }
-
- // Set knot to the center of its zoom rect bounds for all frames
- zf->knot.x = (zf->frame.left + zf->frame.right) / 2;
- zf->knot.y = (zf->frame.top + zf->frame.bottom) / 2;
- }
-
- // Finally, traverse the array, using line segments between knots to
- // choose spline control points that form a "smooth" path.
- // If the two control points on either side of a Bezier spline knot
- // are colinear, then the spline is continuous through the knot.
- // This means the zoom won't too suddenly change direction as at passes
- // each individual ZoomFrame entry in the zoomArray. The hard part
- // is figuring out how to set the line that the two control points
- // have to be on so that the path isn't too crazy. Note that if we
- // have only 2 entries in the array, we don't have any intermediate
- // knots to worry about.
- //
- // Naturally, this code would need to be sped up for older 68K macs,
- // especially since the first time it's called it'll have to load
- // the SANE package, but it seems to work reasonably fast on my
- // PowerBook 180c (68030).
-
- zf = *zoomArray; // It's still locked
- zf->c0 = zf->knot;
-
- for (zf++,k=1; k<(numFrames-1); k++,zf++) {
-
- double theta,dot,cross,sn,cs;
- long dx0,dy0,dx1,dy1;
-
- dx0 = zf->knot.x - (zf-1)->knot.x; // Vector from last knot to this one
- dy0 = zf->knot.y - (zf-1)->knot.y;
- dx1 = (zf+1)->knot.x - zf->knot.x; // Vector from this knot to next one
- dy1 = (zf+1)->knot.y - zf->knot.y;
-
- // Get the angle between the two vectors, (dx0,dy0) --> (dx1,dy1)
- dot = dx0*dx1 + dy0+dy1; // Dot product = len0 * len1 * cos(theta)
- cross = dx0*dy1 - dx1*dy0; // Cross product = len0 * len1 * sin(theta)
- theta = atan2(cross,dot);
-
- // Get the sin and cosine of half the angle
- theta = theta / 2.0;
- sn = sin(theta);
- cs = cos(theta);
-
- // Rotate our initial vector by half the angle, and shrink it to a third
- // its size (purely a heuristic: the larger this vector is, the more
- // boisterous (wider) the Bezier turn will be. The result will be the
- // vector between control points (through the knot) for this frame.
- x = (cs*dx0 - sn*dy0) / 3.0;
- y = sn*dx0 + cs*dy0 / 3.0;
-
- // Set the two colinear control points for the next knot
- (zf-1)->c1.x = zf->knot.x - x;
- (zf-1)->c1.y = zf->knot.y - y;
- zf->c0.x = zf->knot.x + x;
- zf->c0.y = zf->knot.y + y;
- }
-
- zf->c1 = zf->knot;
-
- HUnlock((Handle)zoomArray);
- okay = TRUE; // Tell cleanup not to throw zoomArray away
-
- cleanup:
- if (clipRgn) DisposeRgn(clipRgn);
- if (!okay) {
- DisposeZoom(zoomArray);
- zoomArray = NIL;
- }
-
- return(zoomArray);
- }
-
- /*
- * Throw away the given zoomArray (if it's non-NIL) and throw away all
- * internal clipping regions (and whatever else) as well.
- */
-
- void DisposeZoom(ZoomFrameHandle zoomArray)
- {
- if (zoomArray) {
- long numZooms = GetHandleSize((Handle)zoomArray) / sizeof(ZoomFrame);
- while (numZooms-- > 0) {
- RgnHandle clip = (*zoomArray)[numZooms].clip;
- if (clip) DisposeRgn(clip);
- }
- }
- }
-
- /*
- * Given an array of ZoomFrames, as previously created by NewZoom, animate
- * the zoom on the desktop.
- * The array is always in canonical order from
- * theWindow to theWidget, so if we are opening (as found in theWindow)
- * we have to traverse the array backwards.
- */
-
- void FrameEvadingZoom(ZoomFrameHandle zoomArray, short speed, short qSize)
- {
- Point midCenter,startSize,midSize,endSize,pt;
- Rect midway,clip,content;
- short zoomSteps,zoomSteps2,x,y,w,h,n,numPairs,numFrames,zfInc,q;
- long i,j; Point2D p0,c1,c2,p3,path[MAXPATH+1]; int useDive;
- ZoomFrame *start,*end,*theWindow,*theWidget;
- RgnHandle tmpClip;
-
- // Reality check
- if (zoomArray == NIL)
- return;
-
- // Draw outside of all windows; deskPort becomes current port
- if (!InstallDesktop())
- return;
- // From now on, always return via cleanup so port gets restored
-
- // Fill the clipped rectangle queue with empty rectangles so nothing gets drawn
- // as the queue fills up. The queue makes for a better zoom effect, since there
- // will be more graphic weight to the zooming, and the amount of time any
- // particular zoomrect is displayed is much longer.
-
- if (qSize < 1) qSize = 1;
- SetRect(&qRect[0],0,0,0,0);
- for (q=1; q<qSize; q++)
- qRect[q] = qRect[0];
- // Queued clipping regions 1 through 4 are allocated empty
- for (q=0; q<qSize; q++)
- qClip[q] = NewRgn();
- if (qClip[qSize-1] == NIL) goto cleanup;
-
- // Now do a zoom between each pair of adjacent ZoomRects in the
- // array, in which there is always at least one pair (2 entries).
-
- HLock((Handle)zoomArray);
- numFrames = GetHandleSize((Handle)zoomArray) / sizeof(ZoomFrame);
- numPairs = numFrames - 1;
-
- theWindow = (*zoomArray) + 0;
- theWidget = (*zoomArray) + (numFrames-1);
-
- #ifdef DEBUGONLY
- if (speed > 5)
- {
- start = theWindow;
- end = start + 1;
- for (n=0; n<numPairs; n++,start++,end++) {
- ComputeBezierPath(&start->knot,&start->c0,&start->c1,&end->knot,path,MAXPATH);
- DrawBezierPath(path,MAXPATH);
- }
- Wait(3*speed);
- start = theWindow;
- end = start + 1;
- for (n=0; n<numPairs; n++,start++,end++) {
- ComputeBezierPath(&start->knot,&start->c0,&start->c1,&end->knot,path,MAXPATH);
- DrawBezierPath(path,MAXPATH);
- }
- }
- #endif
-
- // Each iteration slides start/end up or down by one in the array
-
- if (theWindow->opening) {
- start = theWidget;
- zfInc = -1; // Down
- }
- else {
- start = theWindow;
- zfInc = 1; // Up
- }
- end = start + zfInc;
-
- for (n=0; n<numPairs; n++,start+=zfInc,end+=zfInc) {
-
- // Get the starting and ending half sizes
-
- startSize.h = (start->frame.right - start->frame.left) / 2;
- startSize.v = (start->frame.bottom - start->frame.top) / 2;
- endSize.h = (end->frame.right - end->frame.left) / 2;
- endSize.v = (end->frame.bottom - end->frame.top) / 2;
-
- // Knots are the same regardless of opening or closing
- p0 = start->knot;
- p3 = end->knot;
-
- if ((theWindow->opening && start==theWidget) ||
- (!theWindow->opening && end==theWidget)) {
-
- // Get content area minus the scroll bar and grow icon areas
- // This assumes window has both right and bottom scroll bars
- // To be truly general, we should be using a content region.
- content = theWidget->win->portRect;
- content.right -= SCROLLBARWIDTH;
- content.bottom -= SCROLLBARWIDTH;
-
- // Find a rectangle within the window to serve as an intermediate
- // destination for the zoom that is completely contained in the
- // visible area of the destination zoom window. When the zoom
- // reaches midway, we'll change the clipping region.
- midway.left = content.left;
- midway.top = content.top;
- midway.right = (content.right + (theWidget->frame.right-theWidget->frame.left)) / 2;
- midway.bottom = (content.bottom + (theWidget->frame.bottom-theWidget->frame.top)) / 2;
- CenterRect(&midway,&content,&midway);
-
- // If midway is larger than window, just use inset window content bounds
- if (midway.left<content.left || midway.top<content.top ||
- midway.right>content.right || midway.bottom>content.bottom) {
- midway = content;
- InsetRect(&midway,8,8);
- }
- else {
- /* Don't let midway's dimensions get larger than source/destination window's */
- short margin = ((midway.right-midway.left) - (theWindow->frame.right-theWindow->frame.left)) / 2;
- if (margin > 0) {
- midway.left += margin; midway.right -= margin;
- }
- margin = ((midway.bottom-midway.top) - (theWindow->frame.bottom-theWindow->frame.top)) / 2;
- if (margin > 0) {
- midway.top += margin; midway.bottom -= margin;
- }
- }
-
- // Convert to global coords and get center and half-sizes
- LocalToGlobalRect(theWidget->win,&midway);
- midSize.h = (midway.right - midway.left) / 2;
- midSize.v = (midway.bottom - midway.top) / 2;
- midCenter.h = (midway.right + midway.left) / 2;
- midCenter.v = (midway.bottom + midway.top) / 2;
-
- // Get window content as clipping rectangle in global coords
- clip = content;
- LocalToGlobalRect(theWidget->win,&clip);
-
- // But have to take intersection of it with final window clipping region
-
- tmpClip = NewRgn();
- if (tmpClip) {
- RectRgn(tmpClip,&clip);
- SectRgn(theWidget->clip,tmpClip,theWidget->clip);
- DisposeRgn(tmpClip);
- }
-
- // Set control (tension) points to other side of window in global coordinates
- pt.h = (content.left + content.right) / 2;
- pt.v = (content.top + content.bottom) / 2;
-
- if (theWidget->frame.bottom > midway.bottom) pt.v = content.top + CONTROLINSET;
- if (theWidget->frame.top < midway.top) pt.v = content.bottom - CONTROLINSET;
- if (theWidget->frame.right > midway.right) pt.h = content.left + CONTROLINSET;
- if (theWidget->frame.left < midway.left) pt.h = content.right - CONTROLINSET;
-
- PushPort(theWidget->win);
- LocalToGlobal(&pt);
- PopPort();
-
- c1.x = c2.x = pt.h;
- c1.y = c2.y = pt.v;
- useDive = TRUE;
- }
- else {
- // Not final widget window: still evading window frames
- useDive = FALSE;
- // Since the control points for the segment between each pair are
- // stored in only one ZoomFrame, we have to use the fact that
- // we're opening or not to get the right ones.
- if (theWindow->opening) {
- c1 = end->c1; // Traversing array backwards
- c2 = end->c0;
- }
- else {
- c1 = start->c0; // Traversing it forwards
- c2 = start->c1;
- }
- }
-
- // Precompute the spline's path, including both endpoints
-
- ComputeBezierPath(&p0,&c1,&c2,&p3,path,MAXPATH);
-
- // DrawBezierPath(path,MAXPATH);
- // Wait(speed*2);
- // DrawBezierPath(path,MAXPATH);
-
- // You could interpolate among bounding pen sizes if you wanted to give
- // more of a depth effect, but this would probably only be worth doing
- // in a higher resolution world.
- PenSize(start->thickness,start->thickness);
-
- zoomSteps = MAXPATH;
- zoomSteps2 = zoomSteps/2; // Halfway point
-
- for (i=0; i<=zoomSteps; i++) { // loops zoomSteps+1 times
-
- x = path[i].x;
- y = path[i].y;
-
- if (useDive) {
- // If opening, start is theWidget and end is before it in the array.
- // If closing, end is theWidget and start is after it in the array.
- // In either case, we have to change the clipping region half way
- // through the zoom, when it reaches midway.
- if (i <= zoomSteps2) {
- // First half of zoom
- w = ((zoomSteps2-i)*startSize.h + i*midSize.h) / zoomSteps2;
- h = ((zoomSteps2-i)*startSize.v + i*midSize.v) / zoomSteps2;
- SetClip(start->clip);
- }
- else {
- // Second half of zoom: interpolate from midway to end within window
- j = i - zoomSteps2;
- w = ((zoomSteps2-j)*midSize.h + j*endSize.h) / zoomSteps2;
- h = ((zoomSteps2-j)*midSize.v + j*endSize.v) / zoomSteps2;
- SetClip(end->clip);
- }
- }
- else {
- // Get i'th intermediate size (interpolated linearly) for whole zoom
- w = ((zoomSteps-i)*startSize.h + i*endSize.h) / zoomSteps;
- h = ((zoomSteps-i)*startSize.v + i*endSize.v) / zoomSteps;
- // Set clipping to exclude all higher windows
- if (theWindow->opening) SetClip(end->clip);
- else SetClip(start->clip);
- }
-
- GetClip(qClip[qSize-1]);
- SetRect(&qRect[qSize-1],x-w,y-h,x+w,y+h);
- FrameRect(&qRect[qSize-1]);
-
- // Erase (assuming xor mode) the i-(qSize-1)'th previously drawn rectangle
- SetClip(qClip[0]);
- FrameRect(&qRect[0]);
-
- // Shift clipped rectangle queue up by 1, leaving r4 ready to be redefined
- tmpClip = qClip[0];
- for (q=1; q<qSize; q++) {
- qRect[q-1] = qRect[q];
- qClip[q-1] = qClip[q];
- }
- qClip[qSize-1] = tmpClip;
-
- // Use governor so processor speed doesn't affect zoom
- Wait(speed);
- }
- }
-
- HUnlock((Handle)zoomArray);
-
- // Erase last qSize-1 zoomrects to empty the queue of drawn zoomrects
-
- for (q=1; q<qSize; q++) {
- SetClip(qClip[q-1]);
- FrameRect(&qRect[q-1]);
- Wait(speed);
- }
-
- cleanup:
- for (q=0; q<qSize; q++) {
- if (qClip[q]) DisposeRgn(qClip[q]);
- qClip[q] = NIL;
- }
-
- ResetDesktop(); // Restore normal drawing environment
- }
-
- /*
- * Precompute the path of a Bezier segment in 2D coordinates
- * whose starting and ending points are p0 and p3, and whose control points are
- * c1 and c2. The path should be stored in the array "path", which is expected
- * to be able to hold (numPoints+1) elements, from 0 to numPoints, inclusive.
- * numPoints should be a power of 2 between 2 and 32, inclusive. This routine
- * can be easily generalized to 3D (or higher) coordinates, and is optimized
- * for fast computation in (long) integers. Since there are no divides, the
- * routine does the right thing regardless of whether p0, c1, c2, or p3 are
- * all different or coincident or whatever.
- */
-
- void ComputeBezierPath(Point2D *p0, Point2D *c1, Point2D *c2, Point2D *p3,
- Point2D *path, short numPoints)
- {
- long i,ax,ay,bx,by,cx,cy,curx,cury;
- short s1,s2,s3;
-
- curx = p0->x; cury = p0->y;
-
- // Compute the integer Bezier coefficients, a, b, and c
-
- cx = (c1->x - curx); cx += cx << 1; // times 3
- cy = (c1->y - cury); cy += cy << 1;
-
- bx = (c2->x - c1->x); bx += (bx << 1) - cx; // times 3 - c
- by = (c2->y - c1->y); by += (by << 1) - cy;
-
- ax = (p3->x - curx) - cx - bx;
- ay = (p3->y - cury) - cy - by;
-
- if (numPoints == 32) s1 = 5;
- else if (numPoints == 16) s1 = 4;
- else if (numPoints == 8) s1 = 3;
- else if (numPoints == 4) s1 = 2;
- else s1 = 1;
-
- s2 = s1+s1; s3 = s2+s1; // s3 is 15 bits worth of scaling
-
- bx <<= s1; by <<= s1; // Scale operands up for later, according
- cx <<= s2; cy <<= s2; // to the degree in i in loop below
- curx <<= s3; cury <<= s3;
-
- for (i=0; i<=numPoints; i++) {
- path[i].x = (i * (i * (i * ax + bx) + cx) + curx) >> s3;
- path[i].y = (i * (i * (i * ay + by) + cy) + cury) >> s3;
- }
- }
-
- static void DrawBezierPath(Point2D *path, short numPoints)
- {
- Point2D *p = path;
-
- MoveTo(p->x,p->y);
- while (numPoints-- > 0) {
- p++;
- LineTo(p->x,p->y);
- }
- }
-